package com.google.gwt.sample.stockwatcher.client;

import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;

import javax.servlet.http.HttpSession;

import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.JsArray;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;

import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyPressEvent;
import com.google.gwt.event.dom.client.KeyPressHandler;

import com.google.gwt.http.client.URL;
import com.google.gwt.i18n.client.DateTimeFormat;
import com.google.gwt.i18n.client.NumberFormat;

import com.google.gwt.user.client.Cookies;
import com.google.gwt.user.client.HTTPRequest;
import com.google.gwt.user.client.Random;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Anchor;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.http.client.Request;
import com.google.gwt.http.client.RequestBuilder;
import com.google.gwt.http.client.RequestCallback;
import com.google.gwt.http.client.RequestException;
import com.google.gwt.http.client.Response;

public class StockWatcher implements EntryPoint, ClickHandler, KeyPressHandler {
	private VerticalPanel mainPanel = new VerticalPanel();
	private MyFlexTable stocksFlexTable = new MyFlexTable();
	private HorizontalPanel addPanel = new HorizontalPanel();
	private TextBox newSymbolTextBox = new TextBox();
	private Button addStockButton = new Button("Add");
	private Label lastUpdatedLabel = new Label();
	private Label errorMsgLabel = new Label();
	private StockWatcherConstants constants = GWT
			.create(StockWatcherConstants.class);
	private StockWatcherMessages messages = GWT
			.create(StockWatcherMessages.class);
	private static final int REFRESH_INTERVAL = 1000;
	private ArrayList<String> stocks = new ArrayList<String>();
	private ArrayList<StockPrice> sp = new ArrayList<StockPrice>();

	private StockPriceServiceAsync stockPriceSvc = GWT
			.create(StockPriceService.class);

	private int jsonRequestId = 0;
	// private static final String JSON_URL = GWT.getModuleBaseURL()
	// + "stockPrices?q=";
	private static String JSON_URL = "http://localhost:81/HTTP/handlers/AyncHandler.ashx?";
	// "http://localhost:4255/handlers/AyncHandler.ashx?";
	private LoginInfo loginInfo = null;
	private VerticalPanel loginPanel = new VerticalPanel();
	private Label loginLabel = new Label(
			"Please sign in to your Google Account to access the StockWatcher application.");
	private Anchor signInLink = new Anchor("Sign In");
	private Anchor signOutLink = new Anchor("Sign Out");
	private final StockServiceAsync stockService = GWT
			.create(StockService.class);

	public void onModuleLoad() {
		// Check login status using login service.
		// LoginServiceAsync loginService = GWT.create(LoginService.class);
		// loginService.login(GWT.getHostPageBaseURL(),
		// new AsyncCallback<LoginInfo>() {
		// public void onFailure(Throwable error) {
		// handleError(error);
		// }
		//
		// public void onSuccess(LoginInfo result) {
		// loginInfo = result;
		// if (loginInfo.isLoggedIn()) {
		// loadStockWatcher();
		// } else {
		// loadLogin();
		// }
		// }
		// });

		loadStockWatcher();
	}

	private void loadLogin() {
		// Assemble login panel.
		signInLink.setHref(loginInfo.getLoginUrl());
		loginPanel.add(loginLabel);
		loginPanel.add(signInLink);
		RootPanel.get("stockList").add(loginPanel);
	}

	private void loadStockWatcher() {
		// Set the window title, the header text, and the Add button text.
		Window.setTitle(constants.stockWatcher());
		// TODO Create table for stock data.

		// Set up sign out hyperlink.
		// signOutLink.setHref(loginInfo.getLogoutUrl());
		stocksFlexTable.setText(0, 0, constants.symbol());
		stocksFlexTable.setText(0, 1, constants.price());
		stocksFlexTable.setText(0, 2, constants.change());
		stocksFlexTable.setText(0, 3, constants.remove());
		stocksFlexTable.setCellPadding(6);

		// Add styles to elements in the stock list table.
		stocksFlexTable.getRowFormatter().addStyleName(0, "watchListHeader");
		stocksFlexTable.addStyleName("watchList");

		stocksFlexTable.getCellFormatter().addStyleName(0, 1,
				"watchListNumericColumn");
		stocksFlexTable.getCellFormatter().addStyleName(0, 2,
				"watchListNumericColumn");
		stocksFlexTable.getCellFormatter().addStyleName(0, 3,
				"watchListRemoveColumn");

		// loadStocks();
		Date date = new Date();
		JSON_URL += "sSessionID=" + date.getTime() + "&";
		// TODO Assemble Add Stock panel.

		addPanel.add(newSymbolTextBox);
		addPanel.add(addStockButton);
		addPanel.addStyleName("addPanel");
		// Assemble Main panel.
		errorMsgLabel.setStyleName("errorMessage");
		errorMsgLabel.setVisible(false);

		// TODO Assemble Main panel.
		// mainPanel.add(signOutLink);
		mainPanel.add(stocksFlexTable);
		mainPanel.add(addPanel);
		mainPanel.add(lastUpdatedLabel);
		mainPanel.add(errorMsgLabel);
		// TODO Associate the Main panel with the HTML host page.
		RootPanel.get("stockList").add(mainPanel);

		// TODO Move cursor focus to the input box.
		newSymbolTextBox.setFocus(true);
		addStockButton.addClickHandler(this);
		newSymbolTextBox.addKeyPressHandler(this);
		stocksFlexTable.addClickHandler(this);
		/*
		 * // Listen for mouse events on the Add button
		 * addStockButton.addClickHandler(new ClickHandler() { public void
		 * onClick(ClickEvent event) { addStock(); }
		 * 
		 * });
		 * 
		 * // Listen for keyboard events in the inputbox.
		 * newSymbolTextBox.addKeyPressHandler(new KeyPressHandler() { public
		 * void onKeyPress(KeyPressEvent event) { if (event.getCharCode() ==
		 * KeyCodes.KEY_ENTER) { addStock(); } } });
		 */

		// Timer refreshTimer = new Timer() {
		// @Override
		// public void run() {
		// refreshWatchList();
		// // refreshWatchPrice();
		// }
		// };
		//
		// refreshTimer.scheduleRepeating(REFRESH_INTERVAL);

		refreshWatchList();
	}

	private void loadStocks() {
		stockService.getStocks(new AsyncCallback<String[]>() {
			public void onFailure(Throwable error) {
				handleError(error);
			}

			public void onSuccess(String[] symbols) {
				displayStocks(symbols);
			}

		});
	}

	private void displayStocks(String[] symbols) {
		for (String symbol : symbols) {
			displayStock(symbol);
		}

	}

	private void displayPrices(ArrayList<StockPrice> prices) {
		for (StockPrice price : prices) {
			displayPrice(price);
		}
	}

	private void refreshWatchPrice() {
		if (sp.size() == 0) {
			return;
		}

		String url = JSON_URL;

		for (StockPrice price : sp) {
			url += price.getSymbol() + "+";
		}
		url = URL.encode(url);
		RequestBuilder builder = new RequestBuilder(RequestBuilder.GET, url);

		try {
			Request request = builder.sendRequest(null, new RequestCallback() {
				public void onError(Request request, Throwable exception) {
					displayError("Couldn't retrieve JSON");
				}

				public void onResponseReceived(Request request,
						Response response) {
					if (200 == response.getStatusCode()) {
						updateTable(asArrayOfStockData(response.getText()));
					} else {
						displayError("Couldn't retrieve JSON ("
								+ response.getStatusText() + ")");
					}
				}
			});
		} catch (RequestException e) {
			displayError("Couldn't retrieve JSON");
		}

	}

	private void refreshWatchList() {

		if (stocks.size() == 0) {
			return;
		}

		String url = JSON_URL;

		// Append watch list stock symbols to query URL.
		Iterator iter = stocks.iterator();
		while (iter.hasNext()) {
			url += iter.next();
			if (iter.hasNext()) {
				url += "+";
			}
		}

		url = URL.encode(url);

		// TODO Send request to server and handle errors.
		RequestBuilder builder = new RequestBuilder(RequestBuilder.GET, url);

		try {
			Request request = builder.sendRequest(null, new RequestCallback() {
				public void onError(Request request, Throwable exception) {
					displayError("Couldn't retrieve JSON");
				}

				public void onResponseReceived(Request request,
						Response response) {
					if (200 == response.getStatusCode()) {
						// updateTable(asArrayOfStockData(response.getText()));
						updateTable(response.getText());
					} else {
						displayError("Couldn't retrieve JSON ("
								+ response.getStatusText() + ")");
					}
					refreshWatchList();
				}

			});
		} catch (RequestException e) {
			displayError("Couldn't retrieve JSON");
		}
	}

	private void updateTable(String text) {
		// TODO Auto-generated method stub
		String[] mystocks = text.split("\\|\\|");
		for (String mystock : mystocks) {
			updateStock(mystock);
		}
		// Display time stamp showing last refresh.
		lastUpdatedLabel.setText("Last update : "
				+ DateTimeFormat.getMediumDateTimeFormat().format(new Date()));
	}

	private void updateStock(String mystock) {
		String[] fields = mystock.split("\\|");

		try {
			// Make sure the stock is still in the stock table.
			if (!stocks.contains(fields[0])) {
				return;
			}

			int row = stocks.indexOf(fields[0]) + 1;

			// Format the data in the Price and Change fields.
			String priceText = NumberFormat.getFormat("#,##0.00").format(
					Double.parseDouble(fields[1]));
			NumberFormat changeFormat = NumberFormat
					.getFormat("+#,##0.00;-#,##0.00");
			String changeText = changeFormat.format(Double
					.parseDouble(fields[2]));
			String changePercentText = changeFormat.format(Double
					.parseDouble(fields[3]));

			// Populate the Price and Change fields with new data.
			stocksFlexTable.setText(row, 1, priceText);
			Label changeWidget = (Label) stocksFlexTable.getWidget(row, 2);
			changeWidget.setText(changeText + " (" + changePercentText + "%)");

			// Change the color of text in the Change field based on its value.
			// String changeStyleName = "noChange";
			// if (price.getChangePercent() < -0.1f) {
			// changeStyleName = "negativeChange";
			// } else if (price.getChangePercent() > 0.1f) {
			// changeStyleName = "positiveChange";
			// }

			// changeWidget.setStyleName(changeStyleName);
		} catch (Exception e) {
			displayError("Error retrieve JSON: " + e.getMessage());
		}
	}

	/**
	 * Update the Price and Change fields for all rows in the stock table.
	 * 
	 * @param prices
	 *            Stock data for all rows.
	 */
	private void updateTable(JsArray<StockData> prices) {
		for (int i = 0; i < prices.length(); i++) {
			updateTable(prices.get(i));
		}

		// Display time stamp showing last refresh.
		lastUpdatedLabel.setText("Last update : "
				+ DateTimeFormat.getMediumDateTimeFormat().format(new Date()));
	}

	private void updateTable(StockData price) {
		try {
			// Make sure the stock is still in the stock table.
			if (!stocks.contains(price.getSymbol())) {
				return;
			}

			int row = stocks.indexOf(price.getSymbol()) + 1;

			// Format the data in the Price and Change fields.
			String priceText = NumberFormat.getFormat("#,##0.00").format(
					price.getPrice());
			NumberFormat changeFormat = NumberFormat
					.getFormat("+#,##0.00;-#,##0.00");
			String changeText = changeFormat.format(price.getChange());
			String changePercentText = changeFormat.format(price
					.getChangePercent());

			// Populate the Price and Change fields with new data.
			stocksFlexTable.setText(row, 1, priceText);
			Label changeWidget = (Label) stocksFlexTable.getWidget(row, 2);
			changeWidget.setText(changeText + " (" + changePercentText + "%)");

			// Change the color of text in the Change field based on its value.
			String changeStyleName = "noChange";
			if (price.getChangePercent() < -0.1f) {
				changeStyleName = "negativeChange";
			} else if (price.getChangePercent() > 0.1f) {
				changeStyleName = "positiveChange";
			}

			changeWidget.setStyleName(changeStyleName);
		} catch (Exception e) {
			displayError("Error retrieve JSON: " + e.getMessage());
		}
	}

	/**
	 * If can't get JSON, display error message.
	 * 
	 * @param error
	 */
	private void displayError(String error) {
		errorMsgLabel.setText("Error: " + error);
		errorMsgLabel.setVisible(true);
	}

	private void addStock() {
		final String symbol = newSymbolTextBox.getText().toUpperCase();
		newSymbolTextBox.setFocus(true);

		if (!symbol.matches("^[0-9A-Z\\.]{1,10}$")) {
			Window.alert(messages.invalidSymbol(symbol));
			newSymbolTextBox.selectAll();
			return;
		}

		newSymbolTextBox.setText("");

		// TODO Don't add the stock if it's already in the table.
		if (stocks.contains(symbol))
			return;

		// displayStock(symbol);
		// addStock(symbol);

		// TODO Add the stock to the table.
		int row = stocksFlexTable.getRowCount();
		stocks.add(symbol);
		stocksFlexTable.setText(row, 0, symbol);
		stocksFlexTable.setWidget(row, 2, new Label());
		// TODO Add a button to remove this stock from the table.
		Button removeStockButton = new Button("X");
		removeStockButton.setTitle(symbol);
		removeStockButton.addStyleDependentName("remove");
		removeStockButton.addClickHandler(this);
		stocksFlexTable.setWidget(row, 3, removeStockButton);

		// TODO Get the stock price.
		refreshWatchList();

	}

	private void addStock(final String symbol) {
		stockService.addStock(symbol, new AsyncCallback<Void>() {
			public void onFailure(Throwable error) {
				handleError(error);
			}

			public void onSuccess(Void ignore) {
				displayStock(symbol);
			}
		});
	}

	private void displayStock(final String symbol) {
		int row = stocksFlexTable.getRowCount();
		stocks.add(symbol);
		stocksFlexTable.setText(row, 0, symbol);
		stocksFlexTable.setWidget(row, 2, new Label());
		stocksFlexTable.getCellFormatter().addStyleName(row, 1,
				"watchListNumericColumn");
		stocksFlexTable.getCellFormatter().addStyleName(row, 2,
				"watchListNumericColumn");
		stocksFlexTable.getCellFormatter().addStyleName(row, 3,
				"watchListNumericColumn");

		Button removeStockButton = new Button("X");
		removeStockButton.addStyleDependentName("remove");
		removeStockButton.addClickHandler(new ClickHandler() {
			public void onClick(ClickEvent event) {
				int removeIndex = stocks.indexOf(symbol);
				stocks.remove(removeIndex);
				stocksFlexTable.removeRow(removeIndex + 1);
			}
		});
		stocksFlexTable.setWidget(row, 3, removeStockButton);

		refreshWatchList();
	}

	private void displayPrice(final StockPrice price) {
		int row = stocksFlexTable.getRowCount();
		sp.add(price);
		stocksFlexTable.setText(row, 0, price.getSymbol());
		stocksFlexTable.setWidget(row, 2, new Label());
		stocksFlexTable.getCellFormatter().addStyleName(row, 1,
				"watchListNumericColumn");
		stocksFlexTable.getCellFormatter().addStyleName(row, 2,
				"watchListNumericColumn");
		stocksFlexTable.getCellFormatter().addStyleName(row, 3,
				"watchListNumericColumn");

		Button removeStockButton = new Button("X");
		removeStockButton.addStyleDependentName("remove");
		removeStockButton.addClickHandler(new ClickHandler() {
			public void onClick(ClickEvent event) {
				int removeIndex = sp.indexOf(price);
				stocks.remove(removeIndex);
				stocksFlexTable.removeRow(removeIndex + 1);
			}
		});
		stocksFlexTable.setWidget(row, 3, removeStockButton);

		refreshWatchList();

	}

	public void onClick(ClickEvent event) {
		Widget sender = (Widget) event.getSource();

		if (sender == addStockButton) {
			addStock();
		}
		if (sender == stocksFlexTable) {
			Window.alert("Click");
		} else {
			String symbol = sender.getTitle();
			removeStock(symbol);
		}

	}

	private void removeStock(final String symbol) {
		// stockService.removeStock(symbol, new AsyncCallback<Void>() {
		// public void onFailure(Throwable error) {
		// handleError(error);
		// }
		//
		// public void onSuccess(Void ignore) {
		// undisplayStock(symbol);
		// }
		//
		// });
		undisplayStock(symbol);
	}

	private void undisplayStock(final String symbol) {

		int removeIndex = stocks.indexOf(symbol);
		if (removeIndex >= 0) {
			stocks.remove(removeIndex);
			stocksFlexTable.removeRow(removeIndex + 1);
		}
	}

	public void onKeyPress(KeyPressEvent event) {
		Widget sender = (Widget) event.getSource();

		if (sender == newSymbolTextBox) {
			if (event.getCharCode() == KeyCodes.KEY_ENTER) {
				addStock();
			}
		}
	}

	/**
	 * Convert the string of JSON into JavaScript object.
	 */
	private final native JsArray<StockData> asArrayOfStockData(String json) /*-{
		return eval(json);
	}-*/;

	private void handleError(Throwable error) {
		Window.alert(error.getMessage());
		if (error instanceof NotLoggedInException) {
			Window.Location.replace(loginInfo.getLogoutUrl());
		}
	}

}
